#include <sys/types.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include "dirlisting.h"

#ifndef NAME_MAX
#ifdef _XOPEN_NAME_MAX
#define NAME_MAX _XOPEN_NAME_MAX
#else
#define NAME_MAX 255
#endif /* _XOPEN_NAME_MAX */
#endif /* NAME_MAX */

#define WIDTH_OF_NAME_COLUMN 30

static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

static size_t encode_filename (const char *filename, size_t len, char *output);

int directory_listing (const char *document_root, const char *directory, buffer_t *buffer, eSortCriteria sort_criteria, eSortOrder sort_order)
{
	DIR *dp;
	struct dirent *ep;
	filelist_t directories;
	filelist_t files;
	file_t *record;
	struct stat buf;
	struct tm result;
	char path[PATH_MAX];
	char filename[(3 * NAME_MAX) + 1];
	char spaces[WIDTH_OF_NAME_COLUMN];
	size_t len;
	const char *ptr1, *ptr2;
	int ret;
	size_t i;

	snprintf (path, sizeof (path), "%s%s", document_root, directory);
	dp = opendir (path);
	if (!dp) {
		return -1;
	}

	filelist_init (&directories, sort_criteria, sort_order);
	filelist_init (&files, sort_criteria, sort_order);

	while ((ep = readdir (dp)) != NULL) {
		/* Hidden file/directory? */
		if (ep->d_name[0] == '.') {
			continue;
		}

		snprintf (path, sizeof (path), "%s%s/%s", document_root, directory, ep->d_name);
		if (stat (path, &buf) < 0) {
			continue;
		}

		len = strlen (ep->d_name);

		/* Directory? */
		if (S_ISDIR (buf.st_mode)) {
			ret = filelist_insert (&directories, ep->d_name, len, buf.st_size, buf.st_mtime);
		} else {
			ret = filelist_insert (&files, ep->d_name, len, buf.st_size, buf.st_mtime);
		}

		if (ret < 0) {
			/* Couldn't insert directory entry in file list. */
			filelist_free (&files);
			filelist_free (&directories);
			closedir (dp);

			return -1;
		}
	}

	closedir (dp);

#if DEBUG
	printf ("Directory listing:\n");
	printf ("Parent directory\n");

	for (i = 0; i < directories.used; i++) {
		record = &(directories.records[directories.index[i]]);
		printf ("%s/\t\t%lu\t\t%lu\n", record->name, record->size, record->mtime);
	}

	for (i = 0; i < files.used; i++) {
		record = &(files.records[files.index[i]]);
		printf ("%s\t\t%lu\t\t%lu\n", record->name, record->size, record->mtime);
	}
#endif /* DEBUG */

	memset (spaces, ' ', sizeof (spaces));

	if ((buffer_format (buffer, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">"
	                            "<html>"
	                                "<head>"
	                                    "<title>Index of %s</title>"
	                                "</head>"
	                                "<body>"
	                                    "<h1>Index of %s</h1>"
	                                    "<pre>Name", directory, directory) < 0) || \
	(buffer_append_size_bounded_string (buffer, spaces, WIDTH_OF_NAME_COLUMN - (sizeof ("Name") - 1)) < 0) || \
	(buffer_append_size_bounded_string (buffer, "    Last modified        Size<hr>\n", sizeof ("    Last modified        Size<hr>\n") - 1) < 0)) {
		filelist_free (&directories);
		filelist_free (&files);

		return -1;
	}

	/* If not root directory... */
	if ((*directory != '/') || (*(directory + 1))) {
		ptr1 = directory;
		ptr2 = directory;

		while (*ptr2) {
			if (*ptr2 == '/') {
				if (*(ptr2 + 1)) {
					ptr1 = ptr2;
					ptr2++;
				} else {
					break;
				}
			}

			ptr2++;
		}

		len = encode_filename (directory, ptr1 - directory + 1, filename);

		if ((buffer_append_size_bounded_string (buffer, "<a href=\"", sizeof ("<a href=\"") - 1) < 0) || \
		(buffer_append_size_bounded_string (buffer, filename, len) < 0) || \
		(buffer_append_size_bounded_string (buffer, "\">Parent directory</a>\n", sizeof ("\">Parent directory</a>\n") - 1) < 0)) {
			filelist_free (&directories);
			filelist_free (&files);

			return -1;
		}
	}

	for (i = 0; i < directories.used; i++) {
		record = &(directories.records[directories.index[i]]);

		localtime_r (&record->mtime, &result);

		len = encode_filename (record->name, record->namelen, filename);

		if ((buffer_append_size_bounded_string (buffer, "<a href=\"", sizeof ("<a href=\"") - 1) < 0) || \
		(buffer_append_size_bounded_string (buffer, filename, len) < 0) || \
		(buffer_append_size_bounded_string (buffer, "/\">", sizeof ("/\">") - 1) < 0)) {
			filelist_free (&directories);
			filelist_free (&files);

			return -1;
		}

		if (record->namelen + 1 > WIDTH_OF_NAME_COLUMN) {
			if ((buffer_append_size_bounded_string (buffer, record->name, WIDTH_OF_NAME_COLUMN - 4) < 0) || \
			(buffer_append_size_bounded_string (buffer, ".../</a>", sizeof (".../</a>") - 1) < 0)) {
				filelist_free (&directories);
				filelist_free (&files);

				return -1;
			}
		} else {
			if ((buffer_append_size_bounded_string (buffer, record->name, record->namelen) < 0) || \
			(buffer_append_size_bounded_string (buffer, "/</a>", sizeof ("/</a>") - 1) < 0) || \
			(buffer_append_size_bounded_string (buffer, spaces, WIDTH_OF_NAME_COLUMN - record->namelen - 1) < 0)) {
				filelist_free (&directories);
				filelist_free (&files);

				return -1;
			}
		}

		if (buffer_format (buffer, "    %02d-%s-%04d %02d:%02d     -\n", result.tm_mday, months[result.tm_mon], 1900 + result.tm_year, result.tm_hour, result.tm_min) < 0) {
			filelist_free (&directories);
			filelist_free (&files);

			return -1;
		}
	}

	filelist_free (&directories);

	for (i = 0; i < files.used; i++) {
		record = &(files.records[files.index[i]]);

		localtime_r (&record->mtime, &result);

		len = encode_filename (record->name, record->namelen, filename);

		if ((buffer_append_size_bounded_string (buffer, "<a href=\"", sizeof ("<a href=\"") - 1) < 0) || \
		(buffer_append_size_bounded_string (buffer, filename, len) < 0) || \
		(buffer_append_size_bounded_string (buffer, "\">", sizeof ("\">") - 1) < 0)) {
			filelist_free (&directories);
			filelist_free (&files);

			return -1;
		}

		if (record->namelen > WIDTH_OF_NAME_COLUMN) {
			if ((buffer_append_size_bounded_string (buffer, record->name, WIDTH_OF_NAME_COLUMN - 3) < 0) || \
			(buffer_append_size_bounded_string (buffer, "...</a>", sizeof ("...</a>") - 1) < 0)) {
				filelist_free (&files);
				return -1;
			}
		} else {
			if ((buffer_append_size_bounded_string (buffer, record->name, record->namelen) < 0) || \
			(buffer_append_size_bounded_string (buffer, "</a>", sizeof ("</a>") - 1) < 0) || \
			(buffer_append_size_bounded_string (buffer, spaces, WIDTH_OF_NAME_COLUMN - record->namelen) < 0)) {
				filelist_free (&files);
				return -1;
			}
		}

		if (buffer_format (buffer, "    %02d-%s-%04d %02d:%02d    ", result.tm_mday, months[result.tm_mon], 1900 + result.tm_year, result.tm_hour, result.tm_min) < 0) {
			filelist_free (&files);
			return -1;
		}

		if (record->size > 1024 * 1024 * 1024) {
			if (buffer_format (buffer, "%.01fG\n", (float) record->size / (float) (1024.0 * 1024.0 * 1024.0)) < 0) {
				filelist_free (&files);
				return -1;
			}
		} else if (record->size > 1024 * 1024) {
			if (buffer_format (buffer, "%.01fM\n", (float) record->size / (float) (1024.0 * 1024.0)) < 0) {
				filelist_free (&files);
				return -1;
			}
		} else if (record->size > 1024) {
			if (buffer_format (buffer, "%.01fK\n", (float) record->size / (float) (1024.0)) < 0) {
				filelist_free (&files);
				return -1;
			}
		} else {
			if (buffer_format (buffer, "%lu\n", record->size) < 0) {
				filelist_free (&files);
				return -1;
			}
		}
	}

	filelist_free (&files);

	return buffer_append_size_bounded_string (buffer, "<hr></pre></body></html>", sizeof ("<hr></pre></body></html>") - 1);
}

size_t encode_filename (const char *filename, size_t len, char *output)
{
	size_t i;
	size_t size;

	size = 0;

	for (i = 0; i < len; i++) {
		if (filename[i] == '%') {
			output[size++] = '%';
			output[size++] = '2';
			output[size++] = '5';
		} else {
			output[size++] = filename[i];
		}
	}

	return size;
}
